#include "WAsyncHttpClient.h"
#include <sstream>
#include <time.h>

using namespace std;

WAsyncHttpClient::WAsyncHttpClient(mg_mgr *mgr)
{
    _mgr = mgr;
    if (!_mgr)
    {
        _mgr = new mg_mgr();
        mg_mgr_init(_mgr);
        _selfMgrFlag = true;
    }
    mg_timer_init(&_timer, 1000, MG_TIMER_REPEAT, &WAsyncHttpClient::timerEventAdapter, (void *)this);
}

WAsyncHttpClient::~WAsyncHttpClient()
{
    for (auto it = _fd2ContextMap.begin(); it != _fd2ContextMap.end();)
    {
        close_conn(it->second->conn);
        delete it->second;
        it = _fd2ContextMap.erase(it);
    }
    mg_timer_free(&_timer);

    if (_selfMgrFlag)
    {
        mg_mgr_free(_mgr);
        delete _mgr;
        _mgr = nullptr;
    };
}

bool WAsyncHttpClient::run(int timeoutMs)
{
    mg_mgr_poll(_mgr, timeoutMs);
    return true;
}

void WAsyncHttpClient::sendRequest(std::string url, const HttpClientReqMsg& reqMsg, HttpClientCbFun cbFun)
{
    doSendRequset(url, reqMsg, cbFun, false);
}

void WAsyncHttpClient::sendRequestToDwFile(string url, const HttpClientReqMsg &reqMsg, HttpClientCbFun cbFun)
{
    doSendRequset(url, reqMsg, cbFun, true);
}

void WAsyncHttpClient::doSendRequset(std::string url, const HttpClientReqMsg& reqMsg, HttpClientCbFun cbFun, bool chunkFlag)
{
    HttpClientReqContext *context = new HttpClientReqContext();
    context->cbFun = cbFun;
    context->reqMsg = reqMsg;
    context->activeTime = getSysTickCountInMilliseconds();
    context->chunkFlag = chunkFlag;

    struct mg_str host = mg_url_host(url.c_str());
    string dnsStr = "";
    dnsStr.resize(host.len);
    memcpy((char *)dnsStr.c_str(), host.ptr, host.len);
    if (dnsStr.find(":") == string::npos)
    {
        string ipStr = getIpFromDNS(dnsStr);
        size_t index = url.find(dnsStr);
        url.replace(index, dnsStr.size(), ipStr);
        WLogi("WAsyncHttpClient::sendRequest dnsStr is %s, ip is %s, url after replace is %s", dnsStr.c_str(), ipStr.c_str(), url.c_str());
    }
    else
    {
        WLogi("WAsyncHttpClient::sendRequest use ipv6 url: %s", url.c_str());
    }

    context->url = url;
    context->httpClient = this;
    mg_connection *conn = mg_http_connect(_mgr, url.c_str(), &WAsyncHttpClient::httpClientEventCb, (void *)context);
    context->conn = conn;
    _fd2ContextMap[(int64_t)conn->fd] = context;
}

std::string WAsyncHttpClient::buildUrl(const std::string &uri, const std::map<std::string, std::string> &querys)
{
    std::string url = uri;
    if (!querys.empty()) {
        url += "?";
        bool first = true;
        for (const auto& pair : querys) {
            if (!first) {
                url += "&";
            }
            url += WHttpClientCommon::urlEncode(pair.first) + "=" + WHttpClientCommon::urlEncode(pair.second);
            first = false;
        }
    }
    return url;
}

std::string WAsyncHttpClient::getIpFromDNS(const std::string &dnsStr, bool isIpv6First)
{
    std::string ipv6Str = "";
    std::string ipv4Str = "";

    struct addrinfo hints;
    struct addrinfo *result, *rp;

    memset(&hints, 0, sizeof(struct addrinfo));
    // hints.ai_family = AF_INET; // 只返回ipv4地址
    hints.ai_family = AF_UNSPEC; // ipv4、ipv6地址都返回
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;          /* Any protocol */

    int s = getaddrinfo(dnsStr.c_str(), nullptr, &hints, &result);
    if (s != 0)
    {
        WLoge("WAsyncHttpClient::getIpFromDNS getaddrinfo error: %s, reason: %s", dnsStr.c_str(), gai_strerror(s));
        return "";
    }

    char serverIp[46] = {0};
    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        if (rp->ai_family == AF_INET6)
        {
            sockaddr_in6 *sourceaddr = (sockaddr_in6 *)rp->ai_addr;
            inet_ntop(sourceaddr->sin6_family, &(sourceaddr->sin6_addr), serverIp, 46);
            if (ipv6Str.empty())
            {
                ipv6Str = serverIp;
            }
        }
        else
        {
            sockaddr_in* addr_in = (sockaddr_in*)rp->ai_addr;
            inet_ntop(addr_in->sin_family, &addr_in->sin_addr, serverIp, 16);
            if(ipv4Str.empty())
            {
                ipv4Str = serverIp;
            }
        }
    }

    freeaddrinfo(result);

    if (isIpv6First)
    {
        return ipv6Str.empty() ? ipv4Str : ipv6Str;
    }
    else
    {
        return ipv4Str.empty()? ipv6Str : ipv4Str;
    }
}

void WAsyncHttpClient::handleHttpClientEvent(mg_connection *conn, int msgType, void *msgData, void *cbData)
{
    HttpClientReqContext *context = static_cast<HttpClientReqContext *>(cbData);
    if (msgType == MG_EV_CONNECT)
    {
        struct mg_str host = mg_url_host(context->url.c_str());
        if (mg_url_is_ssl(context->url.c_str()))
        {
            struct mg_tls_opts opts;
            opts.ca = nullptr;
            opts.cert = nullptr;
            opts.certkey = nullptr;
            opts.ciphers = nullptr;
            opts.srvname.ptr = nullptr;
            opts.srvname.len = 0;
            mg_tls_init(conn, &opts);
        }

        stringstream ss;
        ss << context->reqMsg.method << " " << buildUrl(context->reqMsg.uri, context->reqMsg.querys) << " HTTP/1.1\r\n";
        ss << "Host: " << host.ptr << "\r\n";
        for (auto hIt = context->reqMsg.headers.begin(); hIt != context->reqMsg.headers.end(); ++hIt)
        {
            ss << hIt->first << ": " << hIt->second << "\r\n";
        }

        if (!context->reqMsg.body.empty())
        {
            ss << "Content-Length: " << context->reqMsg.body.size() << "\r\n";
        }

        ss << "\r\n";

        ss << context->reqMsg.body;

        mg_send(conn, ss.str().c_str(), ss.str().size());
    }
    else if (msgType == MG_EV_HTTP_MSG)
    {
        if (!context->chunkFlag)
        {
            context->activeTime = getSysTickCountInMilliseconds();
            struct mg_http_message *hm = (struct mg_http_message *) msgData;
            logHttpRequestMsg(conn, hm);
            context->rspMsg = parseHttpMsg(conn, hm);
            if (context->cbFun)
            {
                context->cbFun(context->rspMsg);
            }

            conn->is_draining = 1;
        }
    }
    else if (msgType == MG_EV_HTTP_CHUNK)
    {
        struct mg_http_message *hm = (struct mg_http_message *) msgData;
        /*
         * 由于我对mongoose源码1571行左右的修改，导致最后进入MG_EV_HTTP_MSG时，也会额外触发MG_EV_HTTP_CHUNK，
         * 这次hm->chunk.len为0，若触发回调，会对上层存在影响，所以这里多加个hm->chunk.len > 0的条件
         */
        if (context->chunkFlag && (hm->chunk.len > 0))
        {
            context->activeTime = getSysTickCountInMilliseconds();

            if (!context->rspMsg)
            {
                logHttpRequestMsg(conn, hm);
                context->rspMsg = parseHttpMsg(conn, hm, true);
            }
            else
            {
                handleNewChunkData(context->rspMsg, conn, hm);
            }

            if (context->cbFun)
            {
                context->cbFun(context->rspMsg);
            }

            if (context->rspMsg->finishRecvChunk)
            {
                conn->is_draining = 1;
            }
        }
    }
    else if (msgType == MG_EV_CLOSE)
    {
        if (_fd2ContextMap.find((int64_t)conn->fd) != _fd2ContextMap.end())
        {
            _fd2ContextMap.erase((int64_t)conn->fd);
        }
        delete context;
    }
    else if (msgType == MG_EV_ERROR)
    {
        char *errorStr = (char *) msgData;
        WLoge("WHttpServer::RecvHttpRequest mongoose error:%s", errorStr);
        if (_fd2ContextMap.find((int64_t)conn->fd) != _fd2ContextMap.end())
        {
            if(!context->rspMsg)
            {
                context->rspMsg = shared_ptr<HttpClientRspMsg>(new HttpClientRspMsg());
            }

            context->rspMsg->errStatus = 1;
            if (context->cbFun)
            {
                context->cbFun(context->rspMsg);
            }
        }
    }
}

std::shared_ptr<HttpClientRspMsg> WAsyncHttpClient::parseHttpMsg(mg_connection *conn, mg_http_message *httpCbData, bool chunkFlag)
{
    shared_ptr<HttpClientRspMsg> res = shared_ptr<HttpClientRspMsg>(new HttpClientRspMsg());

    std::string codeStr;
    codeStr.resize(httpCbData->uri.len);
    memcpy((char*)codeStr.c_str(), httpCbData->uri.ptr , httpCbData->uri.len);
    res->code = WHttpClientCommon::str2ll(codeStr);

    for(int i = 0; i < MG_MAX_HTTP_HEADERS; i++)
    {
        if (httpCbData->headers[i].name.len == 0 || !httpCbData->headers[i].name.ptr)
        {
            break;
        }
        string name;
        string value;
        name.resize(httpCbData->headers[i].name.len);
        value.resize(httpCbData->headers[i].value.len);
        memcpy((char*)name.c_str(), httpCbData->headers[i].name.ptr, httpCbData->headers[i].name.len);
        memcpy((char*)value.c_str(), httpCbData->headers[i].value.ptr, httpCbData->headers[i].value.len);
        res->headers[name] = value;
        // std::cout << "show headers, " << name << ": " << value << endl;
    }

    size_t contentLen = 0;
    if (res->headers.find("content-length") != res->headers.end())
    {
        contentLen = (int64_t)stoll(res->headers["content-length"]);
    }
    else if (res->headers.find("transfer-encoding") != res->headers.end()) // chunk下载模式
    {
        contentLen = SIZE_MAX;
    }
    else
    {
        contentLen = httpCbData->body.len;
    }

    if (!chunkFlag && (contentLen > 0) && (contentLen != httpCbData->body.len))
    {
        res->errStatus = 1;
        WLoge("WAsyncHttpClient::parseHttpMsg error http body length");
    }

    res->totalBodySize = contentLen;
    if (chunkFlag)
    {
        res->chunk = std::shared_ptr<std::string>(new std::string());
        res->chunk->resize(httpCbData->chunk.len);
        memcpy((char*)res->chunk->c_str(), httpCbData->chunk.ptr, httpCbData->chunk.len);
        conn->recv.len -= httpCbData->chunk.len;
        res->recvChunkSize += httpCbData->chunk.len;
        res->finishRecvChunk = (res->recvChunkSize >= res->totalBodySize);
    }
    else
    {
        res->body.resize(httpCbData->body.len);
        memcpy((char*)res->body.c_str(), httpCbData->body.ptr, httpCbData->body.len);
    }

    return res;
}

void WAsyncHttpClient::logHttpRequestMsg(mg_connection *conn, mg_http_message *httpCbData)
{
    if (httpCbData->message.len < 1024)
    {
        WLogi("WHttpServer::logHttpRequestMsg %s request id:%ld, message: %s", conn->is_tls ? "https" : "http", conn->id, httpCbData->message.ptr);
    }
    else
    {
        char msg[1025] = {0};
        memcpy(msg, httpCbData->message.ptr, 1024);
        WLogi("WHttpServer::logHttpRequestMsg %s request id:%ld, pre 1024 message: %s", conn->is_tls ? "https" : "http", conn->id, msg);
    }
}

uint64_t WAsyncHttpClient::getSysTickCountInMilliseconds()
{
    timespec time;
    int ret = clock_gettime(CLOCK_MONOTONIC, &time);

    if (ret != 0)
    {
       printf("get clock error!\n");
    }
    uint64_t result = ((uint64_t)time.tv_sec) * 1000 + ((uint64_t)time.tv_nsec) / 1000000;
    return result;
}

// 事件处理函数
void WAsyncHttpClient::httpClientEventCb(mg_connection *conn, int msgType, void *msgData, void *cbData)
{
    HttpClientReqContext *context = static_cast<HttpClientReqContext *>(cbData);
    context->httpClient->handleHttpClientEvent(conn, msgType, msgData, cbData);
}

void WAsyncHttpClient::timerEventAdapter(void *ptr)
{
    WAsyncHttpClient *httpClient = static_cast<WAsyncHttpClient *>(ptr);
    httpClient->checkoutRequestTimeout();
}

void WAsyncHttpClient::checkoutRequestTimeout()
{
    int64_t currentMs = getSysTickCountInMilliseconds();
    for (auto it = _fd2ContextMap.begin(); it != _fd2ContextMap.end();)
    {
        if (currentMs - it->second->activeTime > _timeoutSec*1000)
        {
            it->second->conn->is_draining = 1;
            WLoge("WHttpServer::checkoutRequestTimeout timeout:%ld", it->first);
            if(!it->second->rspMsg)
            {
                it->second->rspMsg = shared_ptr<HttpClientRspMsg>(new HttpClientRspMsg());
            }

            shared_ptr<HttpClientRspMsg> &httpClientReqMsgPtr = it->second->rspMsg;
            httpClientReqMsgPtr->errStatus = 2;
            if (it->second->cbFun)
            {
                it->second->cbFun(httpClientReqMsgPtr);
            }
            it = _fd2ContextMap.erase(it);
        }
        else
        {
            ++it;
        }
    }
}

void WAsyncHttpClient::handleNewChunkData(std::shared_ptr<HttpClientRspMsg> rspMsg, mg_connection *conn, mg_http_message *httpCbData)
{
    rspMsg->chunk = std::shared_ptr<std::string>(new std::string());
    rspMsg->chunk->resize(httpCbData->chunk.len);
    memcpy((char*)rspMsg->chunk->c_str(), httpCbData->chunk.ptr, httpCbData->chunk.len);
    conn->recv.len -= httpCbData->chunk.len;
    rspMsg->recvChunkSize += httpCbData->chunk.len;
    rspMsg->finishRecvChunk = (rspMsg->recvChunkSize >= rspMsg->totalBodySize);
};

void WAsyncHttpClient::setTimeout(int sec)
{
    _timeoutSec = sec;
}
